/* 
 * File: qtimetracker.cpp
 * Project: QTimeTracker
 * Author: Steve Sindelar, http://stevesindelar.cz
 * Licence: NewBSD licence.
 */

#include "qtimetracker.h"
#include "itemstablemodel.h"
#include "../QTimeTracker.Core/csvexporter.h"
#include "lengtheditor.h"
#include "aboutdialog.h"
#include <QAction>
#include <QSystemTrayIcon>
#include <QtGlobal>
#include <QInputDialog>
#include <QTableView>
#include <QFileDialog>
#include <QLineEdit>
#include <QDateTimeEdit>
#include <QItemEditorCreatorBase>
#include <QtGui/QMainWindow>
#include <QtGui>

using namespace Core;
using namespace Ui;


//------------------------------------------------------------------------------------------------------//
//--------------------------Constructor and inicialization----------------------------------------------//

QTimeTracker::QTimeTracker(QApplication& application)
    : QMainWindow(0, 0)
{
    ui.setupUi(this);
	this->application = &application;
    this->initMainTable();
    this->initDetailTable();
    this->initMenu();
    this->connectButtonsSignals();
    this->initEditorFactory();
    this->initTimer();
	if (!this->initTray()) 
	{
		QMessageBox::critical(this, tr("Error"), tr("Application was not able to load icons from folder 'images'."));
		this->_isValid = false;
		return;
	}
    this->saved = true;
	this->_isValid = true;
}

bool QTimeTracker::initTray()
{	
	// icons
	QPixmap runningPixmap("./images/running.png");
	QPixmap stopPixmap("./images/stop.png");
	if (runningPixmap.isNull() || stopPixmap.isNull()) 
	{
		return false;
	}
	this->runningIcon = new QIcon(runningPixmap);
	this->stopIcon = new QIcon(stopPixmap);
	this->trayIcon = new QSystemTrayIcon(*this->stopIcon, this);
	this->updateTrayToolTip();
	this->trayIcon->show();
	this->trayInfoShown = false;

	// menu
    this->restoreMinimizeAction = new QAction("", this); // text and signal is set up in setVisible method, that si called at program start

    QAction* quitAction = new QAction(tr("&Quit"), this);
    Q_ASSERT(connect(quitAction, SIGNAL(triggered()), this, SLOT(onClose())));

	QMenu* menu = new QMenu(this);
    menu->addAction(this->ui.actionStopAll);
	menu->addSeparator();
	menu->addAction(this->restoreMinimizeAction);
	menu->addAction(quitAction);
	this->trayIcon->setContextMenu(menu);

	Q_ASSERT(connect(this->trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason))));
	return true;
}

void QTimeTracker::connectButtonsSignals()
{
    // Main table
    Q_ASSERT(connect(this->ui.createNewItemButton, SIGNAL(clicked(bool)), this, SLOT(createNewItem())));
    Q_ASSERT(connect(this->ui.deleteItemButton, SIGNAL(clicked(bool)), this, SLOT(deleteItem())));

    // Detail table
    Q_ASSERT(connect(this->ui.deleteSpanButton, SIGNAL(clicked(bool)), this, SLOT(deleteSpan())));
}

void QTimeTracker::initMenu()
{
    Q_ASSERT(connect(this->ui.actionOpen, SIGNAL(triggered(bool)), this, SLOT(openFile())));
    Q_ASSERT(connect(this->ui.actionSaveAs, SIGNAL(triggered(bool)), this, SLOT(saveFileAs())));
    Q_ASSERT(connect(this->ui.actionSave, SIGNAL(triggered(bool)), this, SLOT(saveFile())));
	Q_ASSERT(connect(this->ui.actionExportToCSV, SIGNAL(triggered(bool)), this, SLOT(exportToCsv())));
    Q_ASSERT(connect(this->ui.startButton, SIGNAL(clicked(bool)), this, SLOT(startItem())));
    Q_ASSERT(connect(this->ui.actionStopAll, SIGNAL(triggered(bool)), this, SLOT(stopAllItems())));
    Q_ASSERT(connect(this->ui.actionExit, SIGNAL(triggered(bool)), this, SLOT(onClose())));

    Q_ASSERT(connect(this->ui.actionViewTimerLog, SIGNAL(triggered(bool)), this->ui.detailFrame, SLOT(setVisible(bool))));
    Q_ASSERT(connect(this->ui.actionViewTimerLog, SIGNAL(triggered(bool)), this->ui.detailSeparator, SLOT(setVisible(bool))));

	Q_ASSERT(connect(this->ui.actionAbout, SIGNAL(triggered(bool)), this, SLOT(showAboutDialog())));
}

void QTimeTracker::initDetailTable()
{
    this->spansModel = new TimeSpansTableModel();
    QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel(this->ui.centralWidget);
    this->spansProxyModel = proxyModel;
    proxyModel->setSourceModel(this->spansModel);
    this->ui.detailTableView->setModel(proxyModel);
    this->ui.detailFrame->setVisible(false);
    this->ui.detailSeparator->setVisible(false);
}

void QTimeTracker::initMainTable()
{
    QTableView* view = this->ui.mainTableView;

    this->itemsModel = new ItemsTableModel(QList<Item*>());
    QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel(this->ui.centralWidget);
    proxyModel->setSourceModel(this->itemsModel);
    this->itemsProxyModel = proxyModel;
    view->setModel(this->itemsProxyModel);

    view->horizontalHeader()->setStretchLastSection(true);
    view->resizeRowsToContents();

    Q_ASSERT(connect(this->ui.mainTableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
        this, SLOT(updateStartButtonText())));
    Q_ASSERT(connect(this->ui.mainTableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
        this, SLOT(showDetail())));
    Q_ASSERT(connect(this->itemsModel, SIGNAL(dataEdited()), this, SLOT(dataEdited())));	
}

void QTimeTracker::initTimer()
{
    this->timer = new QTimer(this);
    this->timer->start(1000);
    Q_ASSERT(connect(this->timer, SIGNAL(timeout()), this, SLOT(timerTimeout())));
}

void QTimeTracker::initEditorFactory()
{
    QItemEditorFactory *factory = new QItemEditorFactory();
    factory->registerEditor(QVariant::Int, new QStandardItemEditorCreator<LengthEditor>());
    factory->registerEditor(QVariant::DateTime, new QStandardItemEditorCreator<QDateTimeEdit>());
    factory->registerEditor(QVariant::String, new QStandardItemEditorCreator<QLineEdit>());
    QItemEditorFactory::setDefaultFactory(factory);
}

QTimeTracker::~QTimeTracker()
{
	delete this->trayIcon;
	delete this->runningIcon;
	delete this->stopIcon;

    delete this->itemsModel;
	delete this->itemsProxyModel;
	delete this->spansModel;
	delete this->spansProxyModel;

	delete this->timer;
}

//------------------------------------------------------------------------------------------------------//
//----------------------------------------Helpers-------------------------------------------------------//

QModelIndex QTimeTracker::getItemsCurrentIndex()
{
    return this->itemsProxyModel->mapToSource(this->ui.mainTableView->selectionModel()->currentIndex());
}

QModelIndex QTimeTracker::getSpansCurrentIndex()
{
    return this->spansProxyModel->mapToSource(this->ui.detailTableView->selectionModel()->currentIndex());
}

void QTimeTracker::showTrayInfo()
{
	if (!this->trayInfoShown)
	{
		this->trayIcon->showMessage("QTimeTracker", 
			tr("The program will keep running in the "
				"system tray. To terminate the program, "
				"choose Quit in the context menu "
				"of the system tray entry."),
				QSystemTrayIcon::Information,
				1000);
		this->trayInfoShown = true;
	}
}

//------------------------------------------------------------------------------------------------------//
//------------------------------------Slots and events--------------------------------------------------//

void QTimeTracker::closeEvent(QCloseEvent *e)
{
	this->showTrayInfo();
}

void QTimeTracker::updateTrayToolTip()
{
	if (this->runningItems.size() == 0)
	{
		this->trayIcon->setToolTip(tr("QTimeTracker: no running items"));
	}
	else
	{
		this->trayIcon->setToolTip(tr("QTimeTracker: %1 running items").arg(this->runningItems.size()));
	}
}

void QTimeTracker::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason) 
	{
    case QSystemTrayIcon::Trigger:
    case QSystemTrayIcon::DoubleClick:
		this->setVisible(!this->isVisible());
        break;
    default:
        ; //we dont care about other actions
    }
}

void QTimeTracker::setVisible(bool visible)
{
	if (visible)
	{
		this->restoreMinimizeAction->setText(tr("Minimize to try"));
		this->restoreMinimizeAction->disconnect();
		Q_ASSERT(connect(this->restoreMinimizeAction, SIGNAL(triggered()), this, SLOT(hide())));
	}
	else
	{
		this->restoreMinimizeAction->setText(tr("Show"));
		this->restoreMinimizeAction->disconnect();
		Q_ASSERT(connect(this->restoreMinimizeAction, SIGNAL(triggered()), this, SLOT(showNormal())));
	}

	QMainWindow::setVisible(visible);
}

void QTimeTracker::onClose()
{
    QString message;
    if (this->runningItems.size() > 0)
    {
        message = tr("There are running items. Do you really want to quit?");
    }
    else if (!this->saved && this->itemsModel->getItems().size() > 0) 
    {
        message = tr("File was not saved, all changes will be lost. Do you really want to quit?");
    }

    if (!message.isEmpty())
    {
        QMessageBox::StandardButton reply = QMessageBox::question(this, tr("Are you sure?"), message,
                            QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
        if (reply != QMessageBox::Yes)
        {
            return;
        }
    }
    
	this->application->quit();
}

void QTimeTracker::stopAllItems()
{
    foreach(Item* i, this->runningItems)
    {
        i->timeSpans.last().stop();
    }
    this->runningItems.clear();
	this->trayIcon->setIcon(*this->stopIcon);
	this->itemsModel->emitLayoutChanged();
	this->updateTrayToolTip();
}

void QTimeTracker::updateStartButtonText()
{
    QModelIndex current = this->getItemsCurrentIndex();        
    if (current.isValid()) 
    {
        if (this->itemsModel->getItem(current)->isRunning()) 
        {
            this->ui.startButton->setText(tr("Stop"));
        }
        else 
        {
            this->ui.startButton->setText(tr("Start"));
        }
    }
}

void QTimeTracker::createNewItem()
{
    this->itemsModel->getItems().push_back(new Item(tr("New item")));
    this->saved = false;
    this->itemsModel->emitLayoutChanged();
    this->ui.mainTableView->resizeRowsToContents();
}

void QTimeTracker::startItem()
{
    QModelIndex index = this->getItemsCurrentIndex();
    if (index.isValid())
    {        
        this->saved = false;
        Item* item = this->itemsModel->getItem(index);
        if (this->runningItems.contains(item)) //Stop
        {
            item->timeSpans.last().stop();
            this->runningItems.removeAll(item);
            this->itemsModel->emitDataChanged(this->itemsModel->getItemRow(item));
            this->spansModel->emitDataChanged(this->spansModel->rowCount()-1);

			// update tray
			if (this->runningItems.size() == 0)
			{
				this->trayIcon->setIcon(*this->stopIcon);
			}
        }
        else //Start
        {
            item->timeSpans.push_back(TimeSpan());
            item->timeSpans.last().start();
            this->spansModel->emitLayoutChanged();
            this->runningItems.push_back(item);

			// update tray
			if (this->runningItems.size() == 1)
			{
				this->trayIcon->setIcon(*this->runningIcon);
			}
        }
    }

	this->updateTrayToolTip();
    this->updateStartButtonText();
}

void QTimeTracker::deleteItem()
{
    QModelIndex index = this->getItemsCurrentIndex();    
    if (index.isValid())
    {
        Item* currentItem = this->itemsModel->getItem(index);
		this->spansModel->setData(NULL);

		//when the item is running, cancel the operation
		if (this->runningItems.contains(currentItem))
		{
			QMessageBox::information(this, tr("Cannot delete item"), 
				tr("This items is running. Please stop it."));
			return;
		}

        //ask only when the item's spans are not empty
        if (currentItem->timeSpans.size() > 0) 
        {
            QString message = tr("Do you really want to delete item ") + "\"" + currentItem->name + "\"";
            int reply = QMessageBox::question(this, tr("Are you sure?"), message,
                            QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
			if (reply != QMessageBox::Yes)
			{
				return;
			}
        }

        this->itemsModel->removeAt(index);
        this->itemsModel->emitLayoutChanged();
    }
}

void QTimeTracker::deleteSpan()
{
    QModelIndex currentSpanIndex = this->getSpansCurrentIndex();
    QModelIndex currentItemIndex = this->getItemsCurrentIndex();
    if (currentSpanIndex.isValid() && currentItemIndex.isValid())
    {     
        Item* currentItem = this->itemsModel->getItem(currentItemIndex);
        if (currentItem->timeSpans[currentSpanIndex.row()].isRunning())
        {
            QString message = tr("You can not delete running span. Please, stop it.");
            QMessageBox::question(this, tr("Invalid operation?"), message, QMessageBox::Ok);
            return;
        }

        QString message = tr("Do you really want to delete the time span?");
        QMessageBox::StandardButton reply = QMessageBox::question(this, tr("Are you sure?"), message,
                        QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);

        if (reply == QMessageBox::Yes)
        {
            currentItem->timeSpans.removeAt(currentSpanIndex.row());
            this->spansModel->emitLayoutChanged();
        }
    }
}

void QTimeTracker::timerTimeout()
{
    if (this->runningItems.size() > 0)
    {
        if (this->ui.detailFrame->isVisible())
        {
            this->spansModel->timerTick();
        }
        if (this->runningItems.size() < this->itemsModel->rowCount()/2)
        {
            foreach(Item* i, this->runningItems)
            {
                this->itemsModel->timerTick(this->itemsModel->getItemRow(i));
            }
        }
        else
        {
            // too many items running rather update whole table
            this->itemsModel->timerTick();
        }
    }
}

//------------------------------------------------------------------------------------------------------//
//-------------------------------------Menu commands----------------------------------------------------//

void QTimeTracker::showAboutDialog()
{
	AboutDialog dialog(this);
	dialog.exec();
}

void QTimeTracker::exportToCsv()
{
	QString path = QFileDialog::getSaveFileName(this);
	if (!path.isEmpty())
	{
		QStringList separators;
		separators<<","<<";"<<"|";
		QString separator = QInputDialog::getItem(this, tr("Separator"), tr("Please choose the separator"), separators);

		CsvExporter exporter(path, separator.at(0).toAscii());
		exporter.exportItems(this->itemsModel->getItems());
	}
}

void QTimeTracker::showDetail() 
{
    this->spansModel->setData(this->itemsModel->getItem(this->getItemsCurrentIndex()));
    this->ui.detailTableView->resizeRowsToContents();
}

void QTimeTracker::openFile()
{
    QString path = QFileDialog::getOpenFileName(this);
    if (!path.isEmpty())
    {
        XmlPersister persister(path);
		try 
		{
			this->itemsModel->setData(persister.load());
		}
		catch(std::exception& ex)
		{
			QMessageBox::critical(this, tr("Error occurred"), 
				tr("Error occurred during parsing xml file. \nDetailed description: %1").arg(ex.what()));
			return;
		}
        this->currentFileName = path;
    }
}

void QTimeTracker::saveFileAs()
{
    QString path = QFileDialog::getSaveFileName(this);
    if (!path.isEmpty())
    {
        XmlPersister persister(path);
        persister.save(this->itemsModel->getItems());
        this->saved = true;
    }
}


void QTimeTracker::saveFile()
{
    if (this->currentFileName.isEmpty())
    {
        this->saveFileAs();        
        return;
    }

    XmlPersister(this->currentFileName).save(this->itemsModel->getItems());
    this->saved = true;
}
